package com.amiu.spring.config;

import java.util.LinkedHashMap;
import java.util.Map;

import net.sf.ehcache.config.CacheConfiguration;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import com.amiu.shiro.MyRealm;
import com.amiu.shiro.PasswordHashHelper;
import com.amiu.shiro.RetryLimitHashedCredentials;
import com.amiu.shiro.ShiroSpringCache;
import com.amiu.shiro.ShiroSpringCacheManager;

@Configuration
public class ShiroConfig {

	@Bean(name="shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(){
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		// 安全管理器 
		shiroFilter.setSecurityManager(securityManager());
		//默认的登陆访问url
		shiroFilter.setLoginUrl("/login");
//		//登陆成功后跳转的url 
		shiroFilter.setSuccessUrl("/index");
//		//没有权限跳转的url
		shiroFilter.setUnauthorizedUrl("/unauth");
		//自定义Filter
//		Map<String, Filter> filters = new LinkedHashMap<>();
//		filters.put("simpleFilter", SimpleFilter());
//		shiroFilter.setFilters(filters);
		
		/**
         * 配置shiro拦截器链，从前往后验证
         * 1、anon  不需要认证
         * 2、authc 需要认证
         * 3、user  验证通过或RememberMe登录的都可以
         * 当应用开启了rememberMe时,用户下次访问时可以是一个user,
         * 但不会是authc,因为authc是需要重新认证的
         * 顺序从上到下,优先级依次降低
         */
		//配置单个filter
//		shiroFilter.setFilterChainDefinitions("/test = authc");
		
		//要用LinkedHashMap，因为filter必须是有序的
		Map<String, String> hashMap = new LinkedHashMap<>();
		hashMap.put("/captcha.jpg", "anon");
		hashMap.put("/commons/**", "anon");
        hashMap.put("/static/**", "anon");
        hashMap.put("/login", "anon");//登录不做权限拦截
        hashMap.put("/regist", "anon");
        hashMap.put("/**", "authc");//全都做权限拦截
        shiroFilter.setFilterChainDefinitionMap(hashMap);
		return shiroFilter;
	}
	
	/**
	 * 这个Bean可以作为本项目通用的加密解密标准,解密在下方myRealm()中配置了
	 * 加密则在需要加密的类中@Autowired即可使用
	 */
	@Bean
	public PasswordHashHelper passwordHashHelper(){
		PasswordHashHelper passwordHashHelper = 
				new PasswordHashHelper();
		//下方两个参数可用配置文件的方式给出
		passwordHashHelper.setAlgorithmName("md5");//设置加密算法
		passwordHashHelper.setHashIterations(1);//设置加密次数
		return passwordHashHelper;
	}
	
	//myRealm中使用了@Autowired，所以也要是bean
	@Bean
	public MyRealm myRealm(){
		
		/**
		 * 设置登录尝试次数限制,RetryLimitHashedCredentials中有
		 * 设置[算法名]、[解密次数]、[允许错误次数]、[锁定分钟],这些参数都能分离出来,
		 * 可以作为配置文件加载，加密代码也能使用
		 */
		//设置我们自己写的：短时间内密码错误次数锁定，设置用户密码解密规则
		RetryLimitHashedCredentials retryLimit = 
				new RetryLimitHashedCredentials(shiroSpringCacheManager());
		//设置用户密码解密规则
		retryLimit.setHashAlgorithmName(
				passwordHashHelper().getAlgorithmName());
		retryLimit.setHashIterations(
				passwordHashHelper().getHashIterations());
		
		retryLimit.setAllowedErrorCount(5);//设置允许错误次数
		retryLimit.setLockSecond(60*10);//设置锁定时间(秒) 
		retryLimit.setRetryLimitCacheName("passwordRetryCache");//设置 cache名称
		
		MyRealm myRealm = new MyRealm();
		myRealm.setCredentialsMatcher(retryLimit);
		
		return myRealm;
	}
	
	@Bean
	public DefaultWebSecurityManager securityManager(){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//配置realm
		securityManager.setRealm(myRealm());

		//配置session管理器
//		securityManager.setSessionManager(sessionManager());

		//配置rememberMe管理器
		//securityManager.setRememberMeManager(rememberMeManager);
		
		//配置缓存管理器
//		securityManager.setCacheManager(getShiroEhcacheManager());
		return securityManager;
	}
	
	/**
	 * 配置shiro-ehcache管理器,这是shiro提供的Ehcache管理器
	 */
	@Bean(name="cacheManger")
	public org.apache.shiro.cache.ehcache.EhCacheManager getShiroEhcacheManager(){
		EhCacheManager ehCacheManager = new EhCacheManager();
		ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
		
		return ehCacheManager;
	}
	/**
	 * 配置shiro-spring cache管理器
	 * 实际上也就是spring cache管理器，shiro有自己的cache管理器，这里把任务分配给
	 * spring cache管理器去做
	 */
//	@Bean 
//	public ShiroSpringCacheManager getShiroSpringCacheManager(
//			org.springframework.cache.CacheManager cacheManager){
//		return new ShiroSpringCacheManager(cacheManager);
//	}
//	
//	@Bean
//	public ShiroSpringCache shiroSpringCache(){
//		return null;
//	} 
	@Bean//(name="cacheManger")
	public ShiroSpringCacheManager shiroSpringCacheManager(){
//		//1、spring提供的ehcacheManager工厂
		EhCacheManagerFactoryBean springEhCacheManagerFactoryBean = 
				new EhCacheManagerFactoryBean();
//		//2、ehCacheManager工厂加载ehcache配置
//		springEhCacheManagerFactoryBean.setConfigLocation(
//				new ClassPathResource("classpath:ehcache.xml"));
//		//3、获取Ehcache原厂的CacheManager
//		net.sf.ehcache.CacheManager ehcacheManager= 
//				springEhCacheManagerFactoryBean.getObject();
		
		/**
		 * 上面一部分的内容由于还未运行EhCacheManagerFactoryBean类中的
		 * afterPropertiesSet()方法，所以我们用getObject()方法是获取不到
		 * net.sf.ehcache.CacheManager的，直接这么获取将会是null。
		 * 
		 * afterPropertiesSet()方法是spring的Bean的初始化机制，要等本类加载完毕后，
		 * 再用这个方法获取资源文件中的内容，具体请看EhCacheManagerFactoryBean源码；
		 * 
		 * 所以我们挪到了getEhCacheManagerFactoryBean()方法中,
		 * getEhCacheManagerFactoryBean()方法是spring中的一个bean，当我们调用这个方法时，
		 * spring内部已经初始化完这个bean了，说明spring已经调用过afterPropertiesSet()方法了，
		 * 所以我们现在能正常获取ehcacheManager了
		 * 下方第5点就能正常获取
		 */
		
		//4、创建Spring的Ehcache管理器SpringEhcacheManager
		org.springframework.cache.ehcache
			.EhCacheCacheManager springEhcacheManager 
				= new EhCacheCacheManager();
		
		
		/**
		 * 5、用spring的类EhCacheManagerFactoryBean获取原厂的ehcacheManager,
		 * EhCacheManagerFactoryBean中只提供了从xml获取cachemanager的方式
		 * 【如果用javaconfig，这里替换为我们自己写的createEhcacheManager()】
		 */
		//5、通过spring提供的EhCacheManagerFactoryBean(ehcache管理器工厂)获取ehcacheManager
		EhCacheManagerFactoryBean ehCacheManagerFactoryBean = 
				getEhCacheManagerFactoryBean();
		net.sf.ehcache.CacheManager ehcacheManager = 
				ehCacheManagerFactoryBean.getObject();
		//6、将原厂EhcacheManager装到springEhcacheManager,
		springEhcacheManager.setCacheManager(ehcacheManager);
		//7、将springEhcacheManager转换为springCacheManager
		org.springframework.cache.CacheManager 
			springCacheManager = springEhcacheManager;
		//8、初始化到我们的ShiroSpring的Cache管理器中，并返回
		return new ShiroSpringCacheManager(springCacheManager);
	}
	
	/**
	 * 我们这里使用的cache是Ehcache，EhCacheManagerFactoryBean是spring提供的，
	 * 用来创建ehcache缓存管理器的
	 */
	@Bean
	public org.springframework.cache.ehcache.EhCacheManagerFactoryBean getEhCacheManagerFactoryBean(){
		/**
		 * spring提供的ehcache缓存管理器工厂，这个类只提供了从
		 * xml中加载cache配置的方法，使用这个类我们无法在java
		 * 代码中配置cache(不用ehcache.xml，而是用java配置)
		 */
		EhCacheManagerFactoryBean ehCacheManagerFactoryBean = 
				new EhCacheManagerFactoryBean();
		//加载ehcache配置，从ehcache.xml中,若需要javaconfig请看方法createEhcacheManager
		ehCacheManagerFactoryBean.setConfigLocation(
				new ClassPathResource("ehcache.xml"));
		return ehCacheManagerFactoryBean;
		/**
		 * spring的Ehcache管理器工厂org.springframework.cache.ehcache.EhCacheManagerFactoryBean是用来生成
		 * Ehcache提供的Cache管理器的(net.sf.ehcache.CacheManager)，然后将EhcacheManager注入到
		 * spring提供的Ehcache管理器，SpringEhcacheManager中
		 */
	}
	/**
	 * 我们自己写了一个java配置的Ehcache缓存管理器,代替ehcache.xml
	 * 使用位置shiroSpringCacheManager中的第5点
	 * @see #shiroSpringCacheManager
	 * 
	 */
	@SuppressWarnings("unused")
	private net.sf.ehcache.CacheManager createEhcacheManager() {
		//由于spring没有提供，我们就自己写一个，下方用到的类全是Ehcache原厂的
		//代替ehcache.xml中cache的配置信息，对应ehcache.xml中的name为passwordRetryCache的cache，查看ehcache.xml
		CacheConfiguration cacheConfiguration = new CacheConfiguration();
		cacheConfiguration.name("passwordRetryCache");
		cacheConfiguration.maxEntriesLocalHeap(2000);
		cacheConfiguration.eternal(false);
		cacheConfiguration.timeToIdleSeconds(30);
		cacheConfiguration.timeToLiveSeconds(0);
		cacheConfiguration.overflowToDisk(false);
		cacheConfiguration.statistics(true);	
		net.sf.ehcache.config.Configuration config = 
				new net.sf.ehcache.config.Configuration();
		//和ehcache.xml里一样，我们还要添加一个默认的cache,还未添加
		//config.defaultCache(defaultCacheConfiguration)
		config.addCache(cacheConfiguration);
		net.sf.ehcache.CacheManager ehcacheCacheManager = net.sf.ehcache.CacheManager.create(config);
		return ehcacheCacheManager;
	}
	
	 /**
     * rememberMe管理器, cipherKey生成见{@code Base64Test.java}
     */
//    @Bean
//    public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
//        CookieRememberMeManager manager = new CookieRememberMeManager();
//        manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
//        manager.setCookie(rememberMeCookie);
//        return manager;
//    }

	
	  /**
     * 记住密码Cookie
     */
//    @Bean
//    public SimpleCookie rememberMeCookie() {
//        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//        simpleCookie.setHttpOnly(true);
//        simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
//        return simpleCookie;
//    }
	
	/**
	 * session管理器，shiro提供了三种实现
	 * 1、DefaultSessionManager：DefaultSecurityManager使用的默认实现，用于JavaSE环境；
	 * 2、ServletContainerSessionManager：DefaultWebSecurityManager使用的session管理器的默认实现，
	 * 		用于Web环境，其直接使用Servlet容器的会话；
	 * 3、DefaultWebSessionManager：用于Web环境的实现，可以替代ServletContainerSessionManager，
	 * 		自己维护着会话，直接废弃了Servlet容器的会话管理。
	 * 
	 * 一般我们用DefaultWebSessionManager，有更多的自定义设置
	 */
	@Bean
	public DefaultWebSessionManager sessionManager(){
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		//为session缓存配置缓存管理器
//		sessionManager.setCacheManager(getEhcacheManager());
		return sessionManager;
	}
	
	@Bean(name = "sessionManager")
	public ServletContainerSessionManager servletSessionManager() {
		return new ServletContainerSessionManager();
	}
	
	/**
	 * FilterRegistrationBean
	 * 
	 * @return
	 */
//	@Bean
//	public FilterRegistrationBean filterRegistrationBean() {
//		FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
//		filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
//		filterRegistration.setEnabled(true);
//		filterRegistration.addUrlPatterns("/*");
//		filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
//		return filterRegistration;
//	}
	
}
